package edu.nju.mutest.util;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SystemUtil {

    /**
     * Traverse a Folder and get all files with specified extension.
     * @param root the root Folder. If root is a file, returns itself if it has the specified extension,
     *             otherwise an empty list.
     * @param ext the c extension.
     * @return a List of {@link File}.
     */
    public static List<File> traverseFolder(File root, String ext) {
        if (root.isFile()) {
            if (root.getAbsolutePath().endsWith(ext)) {
                return List.of(root);
            }
            return new ArrayList<>();
        }
        List<File> ans = new ArrayList<>();
        File[] files;
        if (root.exists() && (files = root.listFiles()) != null) {
            for (File file : files) {
                ans.addAll(traverseFolder(file, ext));
            }
        }
        return ans;
    }

    /**
     * Execute a command. Note that the error stream is merged into the output stream.
     * @param cmd command
     * @return Whether the program exits normally
     */
    public static int execCmd(String... cmd) throws IOException {
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.redirectErrorStream(true);
        Process p = pb.start();
        return execCmd(p);
    }

    /**
     * Execute a command with cmd or Shell.
     *
     * @param cmd command
     * @return Whether the program exits normally
     */
    @SuppressWarnings("UnusedReturnValue")
    public static int execCmd(String cmd) throws IOException {
        String osName = System.getProperty("os.name");
        if (osName.contains("Windows")) {
            return execCmd(Runtime.getRuntime().exec("cmd /c " + cmd));
        }
        else if (osName.contains("Linux")) {
            return execCmd(Runtime.getRuntime().exec("bash -c '" + cmd + "; exit'"));
        }
        System.out.println("invalid OS system: " + osName);
        return -1;
    }

    /**
     * execute the process, wait fot it and output its result.
     *
     * @param p the process
     * @return Whether the program exits normally
     */
    public static int execCmd(Process p) {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), "GBK"))) {
            p.waitFor();
            // Read execution info
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            // 0 means survived, not 0 means killed.
            return p.exitValue();
        }
        catch (Exception e) {
            System.err.println(Arrays.toString(e.getStackTrace()));
        }
        return -1;
    }

    /**
     * compile .java file into .class file
     *
     * @param dst the destination directory (or class output directory) for class files.
     * @param srcFiles folders for .java files.
     * @return the compilation result
     */
    public static boolean compileFiles(File dst, File... srcFiles) throws IOException {
        List<String> cmdList = new ArrayList<>(List.of("javac", "-d", dst.getAbsolutePath()));
        for (File srcFile : srcFiles) {
            cmdList.addAll(
                    SystemUtil.traverseFolder(srcFile, ".java").stream()
                            .map(File::getAbsolutePath)
                            .collect(Collectors.toList())
            );
        }
        return SystemUtil.execCmd(cmdList.toArray(new String[0])) == 0;
    }

    /**
     * write a table to .csv file.
     *
     * @param data a 2-dimensional table. data[0] is the header.
     * @param csvFilePath the file to be written.
     */
    public static void writeCSVFile(List<List<?>> data, String csvFilePath) {
        try (PrintWriter writer = new PrintWriter(new FileWriter(csvFilePath))) {
            for (List<?> row : data) {
                writer.write(row.stream().map(Object::toString).collect(Collectors.joining(",")));
                writer.println();
            }
        } catch (IOException e) {
            System.out.println("An error occurred while writing to the CSV file.");
            System.err.println(Arrays.toString(e.getStackTrace()));
        }
    }
}
